#include "Trace_All.h"

Color TraceAll::TraceRay( const Ray& ray, int depth) const
{
    ContactData data;
    //if we are a shadow ray, we dont need the closest contact!
    if(depth == 10)
        data = m_scene->CheckContact(ray,false);
    else
        data = m_scene->CheckContact(ray);
    // if this ray hit something
    //10 == shadow ray, so as an early out return the 10.5f code
    if(depth ==10)
    {
        Color c;
        if(data.m_hit && data.m_hitTarget->CastsShadows())
            c.m_a = 10.5f;
        return c;
    }
    depth++;
    //Else, we are either a reflection/refraction ray or a source
    if (data.m_hit && depth <= m_depth)   
    {
        Color RETURN_COLOR;
        //check if this is in shadow
        Ray shadow;
        shadow.m_origin = data.m_hitPoint;
        float shade = 1.0f;
        if(data.m_hitTarget->RecievesShadows())
        {
            for( unsigned int i = 0; i < m_scene->GetLights().size(); ++i)
            {
                Vec3 lightDir;
                if(m_scene->GetLights()[i]->IsDirectional())
                    lightDir = m_scene->GetLights()[i]->GetDirection();
                else
                {
                    Vec3 toLight = shadow.m_origin - m_scene->GetLights()[i]->GetPosition();
                    lightDir = toLight.normalise();
                }
                shadow.m_dir = lightDir.GetInverted();
                shadow.m_hitObject = data.m_hitTarget;
                Color Shadow_Test = TraceRay(shadow, 10);
                if(Shadow_Test.m_a == 10.5f)
                    shade -= 0.8f*1.0f/(float)m_scene->GetLights().size();
            }
        }

        bool reflected = false; bool refracted = false;
        //check if we can reflect something
        if(data.m_hitTarget->Reflect() && depth < m_depth)
        {
            Ray reflection; 
            reflected = true;
            reflection.m_origin = data.m_hitPoint;
            reflection.m_dir = ray.m_dir.Reflect(data.m_hitNormal);
            reflection.m_dir.normalise();
            reflection.m_hitObject = data.m_hitTarget;
            
            Color Reflection_Color = TraceRay(reflection, depth+1);        
            depth++;
            // if we do not hit anything with the reflection ray, use the old contact data
            // for the original hit
            if(Reflection_Color.m_a == 1.5f)
            {
                RETURN_COLOR += RayTracer::ShadeThis(data) * shade;
            }
            else //our color has been calculated depending on what we hit with the reflection vector, so return the combo
            {
                RETURN_COLOR += (RayTracer::ShadeThis(data) + 
                    (Reflection_Color *data.m_hitTarget->GetReflectance())) * 0.5f * shade;
            }
        }
        if(data.m_hitTarget->IsTransparent() && depth < m_depth)
        {
            //if we have a refratic index, and refract returns true,
            if( data.m_hitTarget->GetRefractiveIndex() > 0.1f)
            {   
                refracted = true;
                Ray refracted = ray;
                //if refract fails, just use the old ray direction
                refracted.m_dir.Refract(data.m_hitNormal, data.m_hitTarget->GetRefractiveIndex());
                refracted.m_origin = data.m_hitPoint;
                refracted.m_hitObject = data.m_hitTarget;
                Color Refracted_Color = TraceRay(refracted, depth+1);
                depth++;
                if(Refracted_Color.m_a == 1.5f)
                {
                    RETURN_COLOR += RayTracer::ShadeThis(data) * data.m_hitTarget->GetOpacity() * shade;
                }
                else //our color has been calculated depending on what we hit with the REFRACTION vector, so return the combo
                {
                    RETURN_COLOR +=  (RayTracer::ShadeThis(data) + 
                        (Refracted_Color)) * 0.5f * shade;
                }
            }
        }
        if(!reflected && !refracted) //if this is not reflective or transparent
        {
            RETURN_COLOR +=  RayTracer::ShadeThis(data) * shade;
        }
        return RETURN_COLOR;
    }
    else if(depth == 0)
    {
        return m_scene->m_clearColor;
    }
    else
    {
        Color c = m_scene->m_clearColor;
        c.m_a = 1.5f;
        return c;
    }
}
